#ifdef __cplusplus
#include <cstdio>
#else
#include <stdio.h>
#endif

#define POLL_WRITE 1
#define POLL_READ 2
#define POLL_ERROR 4

#ifdef WIN32
#include <windows.h>
#define EWOULDBLOCK WSAEWOULDBLOCK
typedef SOCKET SoxHandle;
#else
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
typedef int SoxHandle;
#endif

void soxInitialize();
void soxCleanup();
int soxLastError();
SoxHandle soxCreateTcpSocket();
SoxHandle soxCreateUdpSocket();
unsigned int soxIpAddress(const char* ip);
unsigned int soxResolveHost(const char* hostName);
const char* soxIpString(unsigned int ip);
void soxCloseSocket(SoxHandle socketId);
void soxShutdownSocket(SoxHandle socketId);
void soxBlockSocket(SoxHandle socketId);
void soxUnblockSocket(SoxHandle socketId);
bool soxTcpConnect(SoxHandle socketId, unsigned int ipAddress, int port);
int soxTcpWrite(SoxHandle socketId, const char* data, size_t len);
int soxTcpWriteString(SoxHandle socketId, const char* str);
int soxTcpRead(SoxHandle socketId, char* out, size_t outLen);
char* soxTcpReadLine(SoxHandle socketId, char* out, size_t outLen);
int soxTcpPeek(SoxHandle socketId, char* out, size_t outLen);
bool soxTcpConnected(SoxHandle socketId);
void soxEnableNagle(SoxHandle socketId);
void soxDisableNagle(SoxHandle socketId);
bool soxBindUdpPort(SoxHandle socketId, int port);
int soxUdpWrite(SoxHandle socketId, const char* data, size_t len, unsigned int ipAddress, int port);
int soxUdpRead(SoxHandle socketId, char* out, size_t outLen);
int soxUdpPeek(SoxHandle socketId, char* out, size_t outLen);
bool soxTcpListen(SoxHandle socketId, int port);
SoxHandle soxTcpAccept(SoxHandle socketId);
void soxUnlockPort(SoxHandle socketId);
unsigned int soxTcpIp(SoxHandle socketId);
#ifdef __cplusplus

#ifndef SOXH
#define SOXH

#include <string>

namespace sox
{

    class IpAddress
    {
    private:

        unsigned int addr;
    public:

        IpAddress(unsigned int ip = 0) {
            addr = ip;
        }

        IpAddress(const std::string& ip) {
            addr = soxIpAddress(ip.c_str());
        }

        IpAddress(in_addr ip) {
            addr = ip.s_addr;
        }

        unsigned int ToLong() const {
            return addr;
        }

        std::string ToString() const {
            return soxIpString(addr);
        }

        static IpAddress ResolveHost(const std::string& hostName) {
            return soxResolveHost(hostName.c_str());
        }
    };

    class Socket
    {
    protected:

        SoxHandle socketId;
    public:

        ~Socket() {
            Close();
            soxCleanup();
        }

        void Close() {
            soxCloseSocket(socketId);
        }

        void Shutdown() {
            soxShutdownSocket(socketId);
        }

        void Block() {
            soxBlockSocket(socketId);
        }

        void Unblock() {
            soxUnblockSocket(socketId);
        }

        SoxHandle GetSocket() const {
            return socketId;
        }
    };

    class TcpSocket:
        public Socket
    {
    public:

        TcpSocket() {
            soxInitialize();
            socketId = soxCreateTcpSocket();

        }

        TcpSocket(SoxHandle socketId) {
            soxInitialize();
            this->socketId = socketId;
        }

        bool Connect(const IpAddress& ipAddress, int port) {
            return soxTcpConnect(socketId, ipAddress.ToLong(), port);
        }

        bool Connected() const {
            return soxTcpConnected(socketId);
        }

        int Write(const char* data, size_t len) {
            return soxTcpWrite(socketId, data, len);
        }

        int Write(const std::string& str) {
            return soxTcpWriteString(socketId, str.c_str());
        }

        int Read(char* out, size_t outLen) {
            return soxTcpRead(socketId, out, outLen);
        }

        std::string ReadLine() {
            char out[0xFFFF];
            return soxTcpReadLine(socketId, out, sizeof(out));
        }

        void EnableNagle() {
            soxEnableNagle(socketId);
        }

        void DisableNagle() {
            soxDisableNagle(socketId);
        }

        sox::IpAddress GetIp() const {
            return soxTcpIp(socketId);
        }

    };

    class ListenSock:
        public Socket
    {
    public:

        ListenSock() {
            soxInitialize();
            socketId = soxCreateTcpSocket();

        }

        ListenSock(SoxHandle socketId) {
            soxInitialize();
            this->socketId = socketId;
        }

        bool Listen(int port) {
            return soxTcpListen(socketId, port);
        }

        TcpSocket* Accept() {
            unsigned int newSock = soxTcpAccept(socketId);
            if(newSock != INVALID_SOCKET)
                return new TcpSocket(newSock);
            return NULL;
        }

        void UnlockPort() {
            soxUnlockPort(socketId);
        }

    };

    class UdpSocket:
        public Socket
    {
    public:

        UdpSocket() {
            soxInitialize();
            socketId = soxCreateUdpSocket();
        }

        UdpSocket(SoxHandle socketId) {
            soxInitialize();
            this->socketId = socketId;
        }

        bool BindPort(int port) {
            return soxBindUdpPort(socketId, port);
        }

        int Write(const char* data, size_t len, const IpAddress& ipAddress, int port) {
            return soxUdpWrite(socketId, data, len, ipAddress.ToLong(), port);
        }

        int Read(char* out, size_t outLen) {
            return soxUdpRead(socketId, out, outLen);
        }

    };

    class SoxPoller
    {
    private:
        fd_set readSet,
               writeSet,
               errorSet;
        int options;
        SoxHandle range;

    public:
        SoxPoller();
        void AddSocket(const Socket* s, int options);
        bool IsSet(const Socket* s, int option);
        int Poll(int timeOut);
    };
}


#endif // SOXH

#endif
